﻿using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Markup;
using System.Windows.Media;
using System.Windows.Shapes;

/**
 *  
 * (c) Florian Kruesch
 * http://www.codeproject.com/KB/silverlight/silverlight_triangle.aspx
 * 
 */

//namespace XamlKru.Controls.Primitives

namespace AzukiMap.Renderer
{
    public class ImageTriangle : Path
    {
        private const string IMAGE_TRIANGLE_CONTENT_TEMPLATE
            = "<ControlTemplate xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"" +
              "		          xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"" +
#if SILVERLIGHT
              "	              xmlns:renderer=\"clr-namespace:AzukiMap.Renderer;assembly=AzukiMap\"" +
#else
                "	              xmlns:renderer=\"clr-namespace:AzukiMap.Renderer;assembly=AzukiMapWPF\"" +
#endif
              "	              TargetType=\"renderer:ImageTriangle\">" +
              "   <Path x:Name=\"PathElement\" " +
              "	      Width=\"100\" Height=\"100\"" +
              "	      Data=\"M 0,0 100,0 0,100 0,0\"" +
              "   >" +
              "   <Path.RenderTransform>" +
              "	    <TransformGroup>" +
              "		    <ScaleTransform x:Name=\"PathScale\" ScaleX=\"1.01\" CenterX=\"25\" CenterY=\"25\" />" +
              "		    <MatrixTransform x:Name=\"PathTransform\" />" +
              "	    </TransformGroup>" +
              "	</Path.RenderTransform>" +
              "	<Path.Fill>" +
              "	    <ImageBrush x:Name=\"PathBrush\" " +
              "			AlignmentX=\"Left\" AlignmentY=\"Top\" Stretch=\"Fill\"" +
              "	    >" +
              "		    <ImageBrush.RelativeTransform>" +
              "		        <MatrixTransform x:Name=\"BrushTransform\"  />" +
              "		    </ImageBrush.RelativeTransform>" +
              "	    </ImageBrush>" +
              "	</Path.Fill>" +
              "    </Path>" +
              "</ControlTemplate>";

        #region Members

        private const double ANTI_SEAM_OVERSCALE = 1d;
        private bool _recalculate = true;

        #endregion

        #region TemplateParts

        /*
        private const string BRUSH_TRANSFORM_ELEMENT_NAME = "BrushTransform";
        private const string IMAGE_BRUSH_ELEMENT_NAME = "PathBrush";
        private const string PATH_SCALE_ELEMENT_NAME = "PathScale";
        private const string PATH_TRANSFORM_ELEMENT_NAME = "PathTransform";
         */

        private MatrixTransform _brushTransform;
        private ScaleTransform _pathScale;
        private MatrixTransform _pathTransform;

        private ImageSource _textureSource;
        private ImageBrush _imageBrush;

        private PointCollection _texturePositions;

        private Point _point1;
        private Point _point2;
        private Point _point3;

        #endregion

        #region Properties

        public Point Point1
        {
            get { return _point1; }
            set
            {
                _point1 = value;
                OnPointChanged();
            }
        }

        public Point Point2
        {
            get { return _point2; }
            set
            {
                _point2 = value;
                OnPointChanged();
            }
        }

        public Point Point3
        {
            get { return _point3; }
            set
            {
                _point3 = value;
                OnPointChanged();
            }
        }

        public PointCollection TexturePositions
        {
            get { return _texturePositions; }
            set
            {
                _texturePositions = value;
                OnTexturePositionsChanged();
            }
        }

        public ImageSource TextureSource
        {
            get { return _textureSource; }
            set
            {
                _textureSource = value;
                OnImageSourceChanged();
            }
        }

        public bool IsClockwise
        {
            get
            {
                Point p1 = Point1, p2 = Point2, p3 = Point3;
                return (p1.X - p3.X)*(p2.Y - p3.Y) - (p1.Y - p3.Y)*(p2.X - p3.X) <= 0d;
            }
        }

        #endregion

        #region Constructors

        public ImageTriangle()
        {
#if SILVERLIGHT
            //Template = (ControlTemplate) XamlReader.Load(IMAGE_TRIANGLE_CONTENT_TEMPLATE);
#else
            //var xmlReader = XmlReader.Create(new StringReader(IMAGE_TRIANGLE_CONTENT_TEMPLATE));
            //Template = (ControlTemplate) XamlReader.Load(xmlReader);
#endif
            //ApplyTemplate();
            CreateBaseControls();

            Point1 = new Point(0, 0);
            Point2 = new Point(200, 0);
            Point3 = new Point(0, 100);

            UpdateTexturePositions(null);
        }

        private void CreateBaseControls()
        {
            // Create a path geometry.
            /*
             <Path.Data>
               <PathGeometry>
                 <PathGeometry.Figures>
                   <PathFigureCollection>
                     <PathFigure IsClosed="True" StartPoint="0,0">
                       <PathFigure.Segments>
                         <PathSegmentCollection>
                           <LineSegment Point="100,0" />
                           <LineSegment Point="0,100" />
                         </PathSegmentCollection>
                       </PathFigure.Segments>
                     </PathFigure>
                   </PathFigureCollection>
                 </PathGeometry.Figures>
               </PathGeometry>
             </Path.Data>
             */

            /*
            _pathScale = (ScaleTransform) GetTemplateChild(PATH_SCALE_ELEMENT_NAME);
            _pathTransform = (MatrixTransform) GetTemplateChild(PATH_TRANSFORM_ELEMENT_NAME);
            _imageBrush = (ImageBrush) GetTemplateChild(IMAGE_BRUSH_ELEMENT_NAME);
            _brushTransform = (MatrixTransform) GetTemplateChild(BRUSH_TRANSFORM_ELEMENT_NAME);
             */

            var geoms = new PathGeometry();
            var figures = new PathFigureCollection();
            var segments = new PathSegmentCollection();
            var s1 = new Point(100, 0);
            var s2 = new Point(0, 100);

            segments.Add(new LineSegment() { Point = new Point(100, 0) });
            segments.Add(new LineSegment() { Point = new Point(0, 100) });

            figures.Add(new PathFigure()
            {
                StartPoint = new Point(0, 0),
                Segments = segments,
                IsClosed = true,
            });

            geoms.Figures = figures;

            // Create a transform
            /*
              <Path.RenderTransform>
                <TransformGroup>" 
                  <ScaleTransform x:Name="PathScale" ScaleX="1.01" CenterX="25" CenterY="25" />
                  <MatrixTransform x:Name="PathTransform" />
                </TransformGroup>
              </Path.RenderTransform>
             */

            var transforms = new TransformGroup();
            _pathScale = new ScaleTransform()
            {
                ScaleX = 1.01,
                CenterX = 25,
                CenterY = 25,
            };
            _pathTransform = new MatrixTransform();

            transforms.Children.Add(_pathScale);
            transforms.Children.Add(_pathTransform);

            /*
              <Path.Fill>
                <ImageBrush x:Name="PathBrush" AlignmentX="Left" AlignmentY="Top" Stretch="Fill" >
                  <ImageBrush.RelativeTransform>
                    <MatrixTransform x:Name="BrushTransform"  />
                  </ImageBrush.RelativeTransform>
                </ImageBrush>
              </Path.Fill>
             */
            _imageBrush = new ImageBrush();
            _imageBrush.AlignmentX = AlignmentX.Left;
            _imageBrush.AlignmentY = AlignmentY.Top;
            _imageBrush.Stretch = Stretch.Fill;

            _brushTransform = new MatrixTransform();

            _imageBrush.RelativeTransform = _brushTransform;

            Width = 100;
            Height = 100;
            Data = geoms;
            RenderTransform = transforms;
            Fill = _imageBrush;
        }

        #endregion

        #region Public methods

        public void SetPoints(Point p1, Point p2, Point p3)
        {
            _recalculate = false;

            Point1 = p1;
            Point2 = p2;
            Point3 = p3;

            UpdateCorners(p1, p2, p3);

            _recalculate = true;
        }

        #endregion

        #region Control overrides

        /*
        private bool _templateAlreadyApplied;

        public override void OnApplyTemplate()
        {
            if (_templateAlreadyApplied)
            {
                throw new InvalidOperationException("The template can only be applied one time.");
            }

            _templateAlreadyApplied = true;

            _pathScale = (ScaleTransform) GetTemplateChild(PATH_SCALE_ELEMENT_NAME);
            _pathTransform = (MatrixTransform) GetTemplateChild(PATH_TRANSFORM_ELEMENT_NAME);

            _imageBrush = (ImageBrush) GetTemplateChild(IMAGE_BRUSH_ELEMENT_NAME);

            _brushTransform = (MatrixTransform) GetTemplateChild(BRUSH_TRANSFORM_ELEMENT_NAME);
        }
         */

        ~ImageTriangle()
        {
            _imageBrush = null;
        }

        #endregion

        #region Implementation

        private void OnPointChanged()
        {
            if (_recalculate)
            {
                UpdateCorners(Point1, Point2, Point3);
            }
        }

        private void OnTexturePositionsChanged()
        {
            UpdateTexturePositions(TexturePositions);
        }

        private void OnImageSourceChanged()
        {
            _imageBrush.ImageSource = TextureSource;
        }

        private void UpdateCorners(Point p1, Point p2, Point p3)
        {
            // set transformation matrix
            double m11 = (p2.X - p1.X)*0.01d;
            double m12 = (p2.Y - p1.Y)*0.01d;
            double m21 = (p3.X - p1.X)*0.01d;
            double m22 = (p3.Y - p1.Y)*0.01d;
            double ox = p1.X;
            double oy = p1.Y;

            _pathTransform.Matrix = new Matrix(m11, m12, m21, m22, ox, oy);

            // scale a little bit, so the borders overlap just enough
            // to remove the seam between triangles

            // find bounding rectangle
            var rect = new Rect(p1, p2);
            rect.Union(p3);

            double w = rect.Width, h = rect.Height;

            _pathScale.ScaleX = w > 0 ? (w + ANTI_SEAM_OVERSCALE)/w : 0;
            _pathScale.ScaleY = h > 0 ? (h + ANTI_SEAM_OVERSCALE)/h : 0;
        }

        private void UpdateTexturePositions(PointCollection positions)
        {
            Point p1, p2, p3;

            if (positions == null)
            {
                p1 = new Point(0, 0);
                p2 = new Point(1, 0);
                p3 = new Point(0, 1);
            }
            else
            {
                if (positions.Count != 3)
                    throw new ArgumentOutOfRangeException("positions");

                p1 = positions[0];
                p2 = positions[1];
                p3 = positions[2];
            }

            double m11 = (p2.X - p1.X);
            double m12 = (p2.Y - p1.Y);
            double m21 = (p3.X - p1.X);
            double m22 = (p3.Y - p1.Y);
            double ox = p1.X;
            double oy = p1.Y;

#if SILVERLIGHT
            Matrix m = new Matrix(m11, m12, m21, m22, ox, oy).Invert();
#else
            var m = new Matrix(m11, m12, m21, m22, ox, oy);
            m.Invert();
#endif

            _brushTransform.Matrix = m;
        }

        #endregion
    }

#if SILVERLIGHT
    public static class Extensions
    {
        public static Matrix Invert(this Matrix m)
        {
            double m11 = m.M22;
            double m12 = -m.M12;
            double m21 = -m.M21;
            double m22 = m.M11;
            double offsetX = m.M21*m.OffsetY - m.M22*m.OffsetX;
            double offsetY = m.M12*m.OffsetX - m.M11*m.OffsetY;
            double s = m.M11*m.M22 - m.M12*m.M21;

            // don't throw, return identity instead
            if (s == 0) return Matrix.Identity;

            return new Matrix(m11/s, m12/s, m21/s, m22/s, offsetX/s, offsetY/s);
        }
    }
#endif
}